package mornd.autowired2;

import com.sun.org.apache.xerces.internal.xs.datatypes.ObjectList;
import lombok.ToString;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.*;

/**
 * @author: mornd
 * @dateTime: 2023/5/12 - 17:49
 *
 *              @Autowired 内层装配原理
 */

@ComponentScan
public class App {
    static Target target = new Target();

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        Target bean = context.getBean(Target.class);

//        testArray(beanFactory);
//        testList(beanFactory);
//        testApplicationContext(beanFactory);
//        testGeneric(beanFactory);
//        testQualifier(beanFactory);
//        testPrimary(beanFactory);

        System.out.println(target);
    }

    @Component
    @ToString
    static class Target {
        @Autowired private Service[] serviceArray;
        @Autowired private List<Service> serviceList;
        @Autowired private ConfigurableApplicationContext applicationContext;
        @Autowired private Dao<Dao.Teacher> dao;
        @Autowired @Qualifier("service3") private Service serviceQualifier;

        @Autowired private Service service;
    }

    private static void testArray(DefaultListableBeanFactory beanFactory) throws Exception {
        DependencyDescriptor dd = new DependencyDescriptor(Target.class.getDeclaredField("serviceArray"), false);
        // 是否是数组类型
        if (dd.getDependencyType().isArray()) {
            // 获取数组存储的类型
            Class<?> componentType = dd.getDependencyType().getComponentType();

            // 根据类型在容器及父容器中查找 bean，返回 beanName
            String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, componentType);

            List<Object> beansList = new ArrayList<>();
            for (String name : names) {
                Object bean = dd.resolveCandidate(name, componentType, beanFactory);
                beansList.add(bean);
            }

            Object obj = beanFactory.getTypeConverter().convertIfNecessary(beansList, dd.getDependencyType());
            Object[] arr = (Object[]) obj;
            System.out.println(arr);

            target.serviceArray = (Service[]) arr;
        }

    }

    private static void testList(DefaultListableBeanFactory beanFactory) throws Exception {
        DependencyDescriptor dd = new DependencyDescriptor(Target.class.getDeclaredField("serviceList"), false);
        if (dd.getDependencyType() == List.class) {
            // 获取 list 的泛型类型
            Class<?> type = dd.getResolvableType().getGeneric(0).resolve();

            // 根据类型在容器及父容器中查找 bean，返回 beanName
            String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type);

            List<Object> beansList = new ArrayList<>();
            for (String name : names) {
                Object bean = beanFactory.getBean(name, type);
                beansList.add(bean);
            }
            System.out.println(beansList);

            target.serviceList = new ArrayList();
            for (Object o : beansList) {
                target.serviceList.add((Service) o);
            }
        }

    }

    private static void testApplicationContext(DefaultListableBeanFactory beanFactory) throws Exception {
        DependencyDescriptor dd = new DependencyDescriptor(Target.class.getDeclaredField("applicationContext"), false);
        if (dd.getDependencyType() == ConfigurableApplicationContext.class) {

            Field field = DefaultListableBeanFactory.class.getDeclaredField("resolvableDependencies");
            field.setAccessible(true);
            Map<Class, Object> map = (Map<Class, Object>) field.get(beanFactory);

            Object o = null;
            for (Map.Entry<Class, Object> entry : map.entrySet()) {
                /**
                 * A.isAssignableFrom(B)
                 * 判断class类型的 A 是否是 B 的父类型或接口类型或相等
                  */
                boolean assignableFrom = entry.getKey().isAssignableFrom(dd.getDependencyType());
                if(assignableFrom) {
                    o = entry.getValue();
                    break;
                }
            }

            target.applicationContext = (ConfigurableApplicationContext) o;
        }

    }

    private static void testGeneric(DefaultListableBeanFactory beanFactory) throws Exception {
        DependencyDescriptor dd = new DependencyDescriptor(Target.class.getDeclaredField("dao"), false);
        Class<?> type = dd.getDependencyType();

        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);

        for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
            // 根据 beanName 获取 beanDefinition
            BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(name);

            // 将 BeanDefinition 与 dependencyDescriptor 做泛型比对是否匹配
            if(resolver.isAutowireCandidate(new BeanDefinitionHolder(beanDefinition, name), dd)) {
                Object dao = dd.resolveCandidate(name, type, beanFactory);
                target.dao = (Dao<Dao.Teacher>) dao;
                System.out.println(dao);
            }

        }

    }

    private static void testQualifier(DefaultListableBeanFactory beanFactory) throws Exception {
        DependencyDescriptor dd = new DependencyDescriptor(Target.class.getDeclaredField("serviceQualifier"), false);
        Class<?> type = dd.getDependencyType();
        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);
        String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type);

        Object bean = null;
        for (String name : names) {
            BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(name);

            if(resolver.isAutowireCandidate(new BeanDefinitionHolder(beanDefinition, name), dd)) {
                bean = dd.resolveCandidate(name, type, beanFactory);
                break;
            }
        }

        System.out.println(bean);

        target.serviceQualifier = (Service) bean;
    }


    /**
     * 优先级 @Qualifier > @primary > 属性名 注入
     */
    private static void testPrimary(DefaultListableBeanFactory beanFactory) throws Exception {
        DependencyDescriptor dd = new DependencyDescriptor(Target.class.getDeclaredField("service"), false);
        Class<?> type = dd.getDependencyType();
        String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type);

        Object bean = null;
        for (String name : names) {
            BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(name);
            if(beanDefinition.isPrimary()) {
                bean = dd.resolveCandidate(name, type, beanFactory);
                break;
            }
        }
        target.service = (Service) bean;
    }
}
